// 
// Copyright (c) 2006-2009 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;

namespace Anculus.Core
{
	/// <summary>
	/// 
	/// </summary>
	public class XmlConfigurationSection : AbstractConfigurationSection
	{
		/// <summary>
		/// 
		/// </summary>
		protected MemoryConfigurationSection buffer;

		/// <summary>
		/// Initializes a new instance of the <see cref="XmlConfigurationSection"/> class.
		/// </summary>
		/// <param name="parent">The parent.</param>
		/// <param name="name">The name.</param>
		/// <param name="reader">The reader.</param>
		/// <param name="ver">The version (currently version 1 and 2 are supported).</param>
		protected XmlConfigurationSection (XmlConfigurationSection parent, string name, XmlReader reader, int ver)
			: base (parent, name)
		{
			buffer = new MemoryConfigurationSection ();
			
			if (reader != null)
			{
				if (ver == 1)
					LoadSectionVersion1 (reader);
				else
					LoadSectionVersion2 (reader);
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="XmlConfigurationSection"/> class.
		/// </summary>
		/// <param name="parent">The parent.</param>
		/// <param name="name">The name.</param>
		protected XmlConfigurationSection (IConfigurationSection parent, string name)
			: base (parent, name)
		{
			buffer = new MemoryConfigurationSection ();
		}

		public override int KeyCount
		{
			get { return buffer.KeyCount; }
		}

		public override bool ContainsKey (string name)
		{
			return buffer.ContainsKey (name);
		}

		public override IEnumerable<string> Keys
		{
			get { return buffer.Keys; }
		}

		public override void Remove (string name)
		{
			buffer.Remove (name);
		}

		protected internal override void SetInternal (string name, ConfigurationObjectType type, object value)
		{
			buffer.SetInternal (name, type, value);
		}

		protected internal override object GetInternal (string name, ConfigurationObjectType type)
		{
			return buffer.GetInternal (name, type);
		}
		
		public override ConfigurationObjectType GetObjectType (string name)
		{
			return buffer.GetObjectType (name);
		}

		protected internal override int CheckType (string name, ConfigurationObjectType type)
		{
			return buffer.CheckType (name, type);
		}

		protected override IConfigurationSection CreateChildSection (string name)
		{
			return new XmlConfigurationSection (this, name, null, 0);
		}
		
		[Obsolete]
		protected void SaveSectionVersion1 (XmlWriter writer)
		{
			foreach (KeyValuePair<string, MemoryObject> pair in buffer.Objects) {
				MemoryObject mem = pair.Value;

				writer.WriteStartElement ("Object");
				writer.WriteAttributeString ("name", pair.Key);
				writer.WriteAttributeString ("type", mem.ObjectType.ToString ());

				XmlSerializer ser = null;
				switch (mem.ObjectType) {
					case ConfigurationObjectType.Bool:
						ser = new XmlSerializer (typeof (bool));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Byte:
						ser = new XmlSerializer (typeof (byte));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Char:
						ser = new XmlSerializer (typeof (char));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.DateTime:
						ser = new XmlSerializer (typeof (long));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Decimal:
						ser = new XmlSerializer (typeof (decimal));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Double:
						ser = new XmlSerializer (typeof (double));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Float:
						ser = new XmlSerializer (typeof (float));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Int:
						ser = new XmlSerializer (typeof (int));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Long:
						ser = new XmlSerializer (typeof (long));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.SByte:
						ser = new XmlSerializer (typeof (sbyte));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Short:
						ser = new XmlSerializer (typeof (short));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.String:
						ser = new XmlSerializer (typeof (string));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.UInt:
						ser = new XmlSerializer (typeof (uint));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.ULong:
						ser = new XmlSerializer (typeof (ulong));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.UShort:
						ser = new XmlSerializer (typeof (ushort));
						ser.Serialize (writer, mem.Value);
						break;
					case ConfigurationObjectType.Null:
						//do nothing
						break;
					case ConfigurationObjectType.Array:
					case ConfigurationObjectType.GenericObject:
					case ConfigurationObjectType.List:
					case ConfigurationObjectType.Object:
					default:
						Type type = mem.Value.GetType ();
						writer.WriteAttributeString ("object-type", type.FullName);
						ser = new XmlSerializer (type);
						ser.Serialize (writer, mem.Value);
						break;
				}

				writer.WriteEndElement ();
			}

			foreach (KeyValuePair<string, IConfigurationSection> pair in _children) {
				XmlConfigurationSection section = pair.Value as XmlConfigurationSection;
				if (section == null || section.parent != this)
					continue;

				writer.WriteStartElement ("Section");
				writer.WriteAttributeString ("name", pair.Key);
				section.SaveSectionVersion1 (writer);
				writer.WriteEndElement ();
			}
		}
		
		protected void SaveSectionVersion2 (XmlWriter writer)
		{
			foreach (KeyValuePair<string, MemoryObject> pair in buffer.Objects) {
				MemoryObject mem = pair.Value;

				switch (mem.ObjectType) {
					case ConfigurationObjectType.Bool:
						writer.WriteStartElement ("bool");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Byte:
						writer.WriteStartElement ("byte");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Char:
						writer.WriteStartElement ("char");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.DateTime:
						writer.WriteStartElement ("datetime");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Decimal:
						writer.WriteStartElement ("decimal");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Double:
						writer.WriteStartElement ("double");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Float:
						writer.WriteStartElement ("float");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Int:
						writer.WriteStartElement ("int");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Long:
						writer.WriteStartElement ("long");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.SByte:
						writer.WriteStartElement ("sbyte");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Short:
						writer.WriteStartElement ("short");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.String:
						writer.WriteStartElement ("string");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.UInt:
						writer.WriteStartElement ("uint");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.ULong:
						writer.WriteStartElement ("ulong");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.UShort:
						writer.WriteStartElement ("ushort");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("value", mem.Value.ToString ());
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Null:
						writer.WriteStartElement ("null");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteEndElement ();
						break;
					case ConfigurationObjectType.Array:
					case ConfigurationObjectType.GenericObject:
					case ConfigurationObjectType.List:
					case ConfigurationObjectType.Object:
					default:
						writer.WriteStartElement ("Serialized");
						writer.WriteAttributeString ("name", pair.Key);
						writer.WriteAttributeString ("type", mem.ObjectType.ToString ());

						Type type = mem.Value.GetType ();
						writer.WriteAttributeString ("object-type", type.AssemblyQualifiedName);
						XmlSerializer ser = new XmlSerializer (type);
						ser.Serialize (writer, mem.Value);
					
						writer.WriteEndElement ();
						break;
				}
			}

			foreach (KeyValuePair<string, IConfigurationSection> pair in _children) {
				XmlConfigurationSection section = pair.Value as XmlConfigurationSection;
				if (section == null || section.parent != this)
					continue;

				writer.WriteStartElement ("Section");
				writer.WriteAttributeString ("name", pair.Key);
				section.SaveSectionVersion2 (writer);
				writer.WriteEndElement ();
			}
		}
		
		protected void LoadSectionVersion1 (XmlReader reader)
		{
			while (reader.Read ()) {
				if (reader.NodeType != XmlNodeType.Element)
					continue;

				if (reader.LocalName == "Section") {
					string name = reader.GetAttribute ("name");

					XmlReader childReader = reader.ReadSubtree ();
					childReader.Read (); //skip ahead
					
					XmlConfigurationSection child = new XmlConfigurationSection (this, name, childReader, 1);
					childReader.Close ();

					this[name] = child;
				} else if (reader.LocalName == "Object") {
					string name = reader.GetAttribute ("name");
					string objectType = reader.GetAttribute ("object-type");
					ConfigurationObjectType type = (ConfigurationObjectType)Enum.Parse (typeof (ConfigurationObjectType), reader.GetAttribute ("type"));

					XmlReader childReader = reader.ReadSubtree ();
					while (childReader.Read ()) {
						if (childReader.NodeType == XmlNodeType.Element && childReader.LocalName != "Object")
							break;
					}

					object value = LoadObjectVersion1 (name, type, objectType, childReader);
					buffer.SetInternal (name, type, value);
					childReader.Close ();
				}
			}
			
			reader.Close ();
		}
		
		protected void LoadSectionVersion2 (XmlReader reader)
		{
			while (reader.Read ()) {
				if (reader.NodeType != XmlNodeType.Element)
					continue;

				string name = reader.GetAttribute ("name");
				string valueString = reader.GetAttribute ("value");
				
				ConfigurationObjectType type = ConfigurationObjectType.Unknown;
				object value = null;
				bool setValue = false;
				
				switch (reader.LocalName) {
				case "Section":
					XmlReader sectionReader = reader.ReadSubtree ();
					sectionReader.Read (); //skip ahead
					
					XmlConfigurationSection child = new XmlConfigurationSection (this, name, sectionReader, 2);
					sectionReader.Close ();

					this[name] = child;

					break;
				case "Serialized":
					string objectType = reader.GetAttribute ("object-type");
					Type realType = Type.GetType (objectType);
					if (realType == null) {
						Log.Error ("Unable to load type '{0}'", objectType);
						break;
					}
					
					type = (ConfigurationObjectType)Enum.Parse (typeof (ConfigurationObjectType), reader.GetAttribute ("type"));

					XmlReader objectReader = reader.ReadSubtree ();
					while (objectReader.Read ()) {
						if (objectReader.NodeType == XmlNodeType.Element && objectReader.LocalName != "Serialized")
							break;
					}

					XmlSerializer ser = new XmlSerializer (realType);
					value = ser.Deserialize (objectReader);
					setValue = true;
					
					objectReader.Close ();

					break;
				case "bool":
				{
					bool result = false;
					if (bool.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Bool;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse boolean value '{0}'", valueString);
					}
					break;
				}
				case "byte":
				{
					byte result = byte.MinValue;
					if (byte.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Byte;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse byte value '{0}'", valueString);
					}
					break;
				}
				case "char":
				{
					char result = char.MinValue;
					if (char.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Char;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse char value '{0}'", valueString);
					}
					break;
				}
				case "datetime":
				{
					long result = 0;
					if (long.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.DateTime;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse DateTime value '{0}'", valueString);
					}
					break;
				}
				case "decimal":
				{
					decimal result = 0;
					if (decimal.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Decimal;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse decimal value '{0}'", valueString);
					}
					break;
				}
				case "double":
				{
					double result = 0;
					if (double.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Double;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse double value '{0}'", valueString);
					}
					break;
				}
				case "float":
				{
					float result = 0;
					if (float.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Float;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse float value '{0}'", valueString);
					}
					break;
				}
				case "int":
				{
					int result = 0;
					if (int.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Int;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse integer value '{0}'", valueString);
					}
					break;
				}
				case "long":
				{
					long result = 0;
					if (long.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Long;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse long value '{0}'", valueString);
					}
					break;
				}
				case "sbyte":
				{
					sbyte result = sbyte.MinValue;
					if (sbyte.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.SByte;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse sbyte value '{0}'", valueString);
					}
					break;
				}
				case "short":
				{
					short result = 0;
					if (short.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.Short;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse short value '{0}'", valueString);
					}
					break;
				}
				case "string":
				{
					value = valueString;
					type = ConfigurationObjectType.String;
					setValue = true;
					break;
				}
				case "uint":
				{
					uint result = 0;
					if (uint.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.UInt;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse uint value '{0}'", valueString);
					}
					break;
				}
				case "ulong":
				{
					ulong result = 0;
					if (ulong.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.ULong;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse ulong value '{0}'", valueString);
					}
					break;
				}
				case "ushort":
				{
					ushort result = 0;
					if (ushort.TryParse (valueString, out result)) {
						value = result;
						type = ConfigurationObjectType.UShort;
						setValue = true;
					} else {
						Log.Warn ("Unable to parse ushort value '{0}'", valueString);
					}
					break;
				}
				case "null":
					value = null;
					type = ConfigurationObjectType.Null;
					setValue = true;
					break;
				}

				if (setValue)
					buffer.SetInternal (name, type, value);
			}
			
			reader.Close ();
		}

		protected object LoadObjectVersion1 (string name, ConfigurationObjectType type, string objectType, XmlReader childReader)
		{
			XmlSerializer ser = null;
			switch (type) {
				case ConfigurationObjectType.Bool:
					ser = new XmlSerializer (typeof (bool));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Byte:
					ser = new XmlSerializer (typeof (byte));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Char:
					ser = new XmlSerializer (typeof (char));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.DateTime:
					ser = new XmlSerializer (typeof (long));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Decimal:
					ser = new XmlSerializer (typeof (decimal));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Double:
					ser = new XmlSerializer (typeof (double));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Float:
					ser = new XmlSerializer (typeof (float));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Int:
					ser = new XmlSerializer (typeof (int));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Long:
					ser = new XmlSerializer (typeof (long));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.SByte:
					ser = new XmlSerializer (typeof (sbyte));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Short:
					ser = new XmlSerializer (typeof (short));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.String:
					ser = new XmlSerializer (typeof (string));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.UInt:
					ser = new XmlSerializer (typeof (uint));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.ULong:
					ser = new XmlSerializer (typeof (ulong));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.UShort:
					ser = new XmlSerializer (typeof (ushort));
					return ser.Deserialize (childReader);
				case ConfigurationObjectType.Null:
					return null;
				case ConfigurationObjectType.Array:
				case ConfigurationObjectType.List:
				case ConfigurationObjectType.GenericObject:
				case ConfigurationObjectType.Object:
				default:
					Type realType = Type.GetType (objectType);
					ser = new XmlSerializer (realType);
					return ser.Deserialize (childReader);
			}
		}
	}
}
